home *** CD-ROM | disk | FTP | other *** search
- #import "TextGraphic.h"
- #import "GraphicView.h"
- #import <appkit/Application.h>
- #import <appkit/Cell.h>
- #import <appkit/Cursor.h>
- #import <appkit/Font.h>
- #import <appkit/Text.h>
- #import <appkit/nextstd.h>
- #import <dpsclient/wraps.h>
-
- @implementation TextGraphic
- /*
- * This uses a text object to draw and edit text.
- *
- * The one quirky thing to understand here is that growable Text objects
- * in 1.0 must be subviews of flipped view. Since a GraphicView is not
- * flipped, we must have a flipped view into the view heirarchy when we
- * edit (this editing view is permanently installed as a subview of the
- * GraphicView--see GraphicView's newFrame: method).
- */
-
- + initialize
- {
- [self setVersion:1]; /* class version, see read: */
- return self;
- }
-
- static id drawText = nil; /* shared Text object used for drawing */
- static id drawWindow = nil;
-
- static void initClassVars()
- /*
- * Create the class variable drawText here.
- */
- {
- if (!drawText) {
- drawText = [Text new];
- [drawText setMonoFont:NO];
- [drawText setEditable:NO];
- [drawText setSelectable:NO];
- [drawText setFlipped:YES];
- }
- }
-
- - init
- {
- initClassVars();
- [super init];
- return self;
- }
-
- - free
- {
- free(data);
- return [super free];
- }
-
- /* Factory methods overridden from superclass */
-
- + (BOOL)isEditable
- {
- return YES;
- }
-
- + cursor
- {
- return NXIBeam;
- }
-
- /* Factory method used to show/hide the ruler */
-
- + hideRuler:view
- /*
- * Tries to hide any rulers that are lying around.
- * If view is nil, obviously it won't hide the ruler
- * (we use this fact to cancel a previous request that
- * we might have made to hide the ruler).
- */
- {
- [view tryToPerform:@selector(hideRuler:) with:nil];
- return self;
- }
-
- /* Instance methods overridden from superclass */
-
- - (BOOL)create:(NXEvent *)event in:view
- /*
- * We are only interested in where the mouse goes up, that's
- * where we'll start editing.
- */
- {
- NXRect viewBounds;
-
- event = [NXApp getNextEvent:NX_MOUSEUPMASK];
- bounds.size.width = bounds.size.height = 0.0;
- bounds.origin = event->location;
- [view convertPoint:&bounds.origin fromView:nil];
- [view getBounds:&viewBounds];
- gFlags.selected = NO;
-
- return NXMouseInRect(&bounds.origin, &viewBounds, NO);
- }
-
- - (BOOL)edit:(NXEvent *)event in:view
- /*
- * Here we are going to use the shared field editor for the window to
- * edit the text in the TextGraphic. First, we must end any other editing
- * that is going on with the field editor in this window using endEditingFor:.
- * Next, we get the field editor from the window. Normally, the field
- * editor ends editing when carriage return is pressed. This is due to
- * the fact that its character filter is NXFieldFilter. Since we want our
- * editing to be more like an editor (and less like a Form or TextField),
- * we set the character filter to be NXEditorFilter. What is more, normally,
- * you can't change the font of a TextField or Form with the FontPanel
- * (since that might interfere with any real editable Text objects), but
- * in our case, we do want to be able to do that. We also want to be
- * able to edit rich text, so we issue a setMonoFont:NO. Editing is a bit
- * more efficient if we set the Text object to be opaque. Note that
- * in textDidEnd:endChar: we will have to set the character filter,
- * FontPanelEnabled and mono-font back so that if there were any forms
- * or TextFields in the window, they would have a correctly configured
- * field editor.
- *
- * To let the field editor know exactly where editing is occurring and how
- * large the editable area may grow to, we must calculate and set the frame
- * of the field editor as well as its minimum and maximum size.
- *
- * We load up the field editor with our rich text (if any).
- *
- * Finally, we set self as the delegate (so that it will receive the
- * textDidEnd:endChar: message when editing is completed) and either
- * pass the mouse-down event onto the Text object, or, if a mouse-down
- * didn't cause editing to occur (i.e. we just created it), then we
- * simply put the blinking caret at the beginning of the editable area.
- *
- * The line marked with the "ack!" is kind of strange, but is necessary
- * since growable Text objects only work when they are subviews of a flipped
- * view.
- *
- * This is why GraphicView has an "editView" which is a flipped view that it
- * inserts as a subview of itself for the purposes of providing a superview
- * for the Text object. The "ack!" line converts the bounds of the TextGraphic
- * (which are in GraphicView coordinates) to the coordinates of the Text
- * object's superview (the editView). This limitation of the Text object
- * will be fixed post-1.0. Note that the "ack!" line is the only one
- * concession we need to make to this limitation in this method (there are
- * two more such lines in textDidEnd:endChar:).
- */
- {
- id fe;
- NXSize maxSize;
- NXStream *stream;
- NXRect viewBounds, frame;
-
- /* Get the field editor in this window. */
-
- [[view window] endEditingFor:self];
- fe = [[view window] getFieldEditor:YES for:self];
- if (!fe) return NO;
- [fe setFont:[[FontManager new] selFont]];
-
- /* Show text ruler and abort hiding the ruler if we have recently ended editing. */
-
- [[self class] perform:@selector(hideRuler:) with:nil afterDelay:0 cancelPrevious:YES];
-
- /* Modify it so that it will edit Rich Text and use the FontPanel. */
-
- [fe setCharFilter:NXEditorFilter];
- [fe setFontPanelEnabled:YES];
- [fe setMonoFont:NO];
- [fe setOpaque:YES];
-
- /*
- * Determine the minimum and maximum size that the Text object can be.
- * We let the Text object grow out to the edges of the GraphicView,
- * but no further.
- */
-
- [view getBounds:&viewBounds];
- maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x;
- maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y;
- if (!bounds.size.height && !bounds.size.width) {
- bounds.origin.y -= floor([fe lineHeight] / 2.0);
- bounds.size.height = [fe lineHeight];
- bounds.size.width = 5.0;
- }
- frame = bounds;
- [view convertRect:&frame fromView:[view superview]]; // ack!
- [fe setMinSize:&bounds.size];
- [fe setMaxSize:&maxSize];
- [fe setFrame:&frame];
- [fe setVertResizable:YES];
-
- /*
- * If we already have text, then put it in the Text object (allowing
- * the Text object to grow downward if necessary), otherwise, put
- * no text in, set some initial parameters, and allow the Text object
- * to grow horizontally as well as vertically
- */
-
- if (data) {
- [fe setHorizResizable:NO];
- stream = NXOpenMemory(data, length, NX_READONLY);
- [fe readRichText:stream];
- NXCloseMemory(stream, NX_SAVEBUFFER);
- } else {
- [fe setHorizResizable:YES];
- [fe setText:""];
- [fe setAlignment:NX_LEFTALIGNED];
- [fe setSelColor:[self textColor]];
- [fe unscript:self];
- }
-
- /*
- * Add the Text object to the view heirarchy and set self as its delegate
- * so that we will receive the textDidEnd:endChar: message when editing
- * is finished.
- */
-
- [fe setDelegate:self];
- [view addSubview:fe];
-
- /*
- * Make it the first responder.
- */
-
- [[view window] makeFirstResponder:fe];
-
- /* Change the ruler to be a text ruler. */
-
- [fe tryToPerform:@selector(showTextRuler:) with:fe];
-
- /*
- * Either pass the mouse-down event on to the Text object, or set
- * the selection at the beginning of the text.
- */
-
- if (event) {
- [fe selectNull]; /* eliminates any existing selection */
- [fe mouseDown:event];
- } else {
- [fe setSel:0:0];
- }
-
- return YES;
- }
-
- - draw
- /*
- * If the region has already been created, then we must draw the text.
- * To do this, we first load up the shared drawText Text object with
- * our rich text. We then set the frame of the drawText object
- * to be our bounds. Finally, we add the Text object as a subview of
- * the view that is currently being drawn in ([NXApp focusView])
- * and tell the Text object to draw itself. We then remove the Text
- * object view from the view heirarchy.
- */
- {
- NXStream *stream;
-
- if (data) {
- stream = NXOpenMemory(data, length, NX_READONLY);
- [drawText readRichText:stream];
- NXCloseMemory(stream, NX_SAVEBUFFER);
- [drawText setFrame:&bounds];
- [[NXApp focusView] addSubview:drawText];
- [drawText display];
- [drawText removeFromSuperview];
- if (DrawStatus == Resizing) {
- PSsetgray(NX_LTGRAY);
- NXFrameRect(&bounds);
- }
- }
-
- return self;
- }
-
- - performTextMethod:(SEL)aSelector with:(void *)anArgument
- {
- int maxlen;
- char *buffer;
- NXStream *stream;
-
- if (data) {
- stream = NXOpenMemory(data, length, NX_READONLY);
- [drawText readRichText:stream];
- NXCloseMemory(stream, NX_SAVEBUFFER);
- [drawText setFrame:&bounds];
- if (!drawWindow) drawWindow = [Window new];
- [[drawWindow contentView] addSubview:drawText];
- [drawText selectAll:self];
- [drawText perform:aSelector with:anArgument];
- [drawText removeFromSuperview];
- [drawText setSel:0 :0];
- font = [drawText font];
- stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
- [drawText writeRichText:stream];
- NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
- NX_ZONEMALLOC([self zone], data, char, length);
- bcopy(buffer, data, length);
- NXCloseMemory(stream, NX_FREEBUFFER);
- }
-
- return self;
- }
-
- - changeFont:sender
- {
- [self performTextMethod:@selector(changeFont:) with:sender];
- return self;
- }
-
- - font
- {
- NXStream *stream;
-
- if (!font && data) {
- stream = NXOpenMemory(data, length, NX_READONLY);
- [drawText readRichText:stream];
- NXCloseMemory(stream, NX_SAVEBUFFER);
- [drawText setSel:0 :0];
- font = [drawText font];
- }
-
- return font;
- }
-
- - (BOOL)isOpaque
- /*
- * We are never opaque.
- */
- {
- return NO;
- }
-
- - (BOOL)isValid
- /*
- * Any size TextGraphic is valid (since we fix up the size if it is
- * too small in our override of create:in:).
- */
- {
- return YES;
- }
-
- - (NXColor)textColor
- {
- return NX_COLORBLACK;
- }
-
- - (NXColor)lineColor
- {
- return NX_COLORCLEAR;
- }
-
- - (NXColor)fillColor
- {
- return NX_COLORCLEAR;
- }
-
- - (BOOL)wantsTextColor
- {
- return YES;
- }
-
- - (BOOL)wantsLineColor
- {
- return NO;
- }
-
- - (BOOL)wantsFillColor
- {
- return NO;
- }
-
- /* Text object delegate methods */
-
- /*
- * If we have more than one line, turn off horizontal resizing.
- */
- - textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect
- {
- NXSelPt start,end;
-
- [textObject getSel:&start :&end];
- if(start.line || end.line)
- [textObject setHorizResizable:NO];
- return self;
- }
-
- - textDidEnd:textObject endChar:(unsigned short)endChar
- /*
- * This method is called when ever first responder is taken away from a
- * currently editing TextGraphic (i.e. when the user is done editing and
- * chooses to go do something else). We must extract the rich text the user
- * has typed from the Text object, and store it away. We also need to
- * get the frame of the Text object and make that our bounds (but,
- * remember, since the Text object must be a subview of a flipped view,
- * we need to convert the bounds rectangle to the coordinates of the
- * unflipped GraphicView). If the Text object is empty, then we remove
- * this TextGraphic from the GraphicView and delayedFree: it.
- * We must remove the Text object from the view heirarchy and, since
- * this Text object is going to be reused, we must set its delegate
- * back to nil.
- *
- * For further explanation of the two "ack!" lines, see edit:in: above.
- */
- {
- int maxlen;
- char *buffer;
- NXStream *stream;
- NXRect oldBounds;
- id editView, graphicView;
-
- if (data) {
- NX_FREE(data);
- data = NULL;
- }
-
- editView = [textObject superview];
- graphicView = [editView superview]; // ack!
-
- if ([textObject textLength]) {
- stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
- [textObject writeRichText:stream];
- NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
- NX_ZONEMALLOC([self zone], data, char, length);
- bcopy(buffer, data, length);
- NXCloseMemory(stream, NX_FREEBUFFER);
- oldBounds = bounds;
- [textObject getFrame:&bounds];
- [editView convertRect:&bounds toView:graphicView]; // ack!
- NXUnionRect(&bounds, &oldBounds);
- [graphicView cache:&oldBounds];
- [[graphicView window] flushWindow];
- [graphicView dirty];
- } else {
- [graphicView removeGraphic:self];
- }
-
- [[self class] perform:@selector(hideRuler:) with:graphicView afterDelay:500 cancelPrevious:YES];
-
- [textObject removeFromSuperview];
- [textObject setDelegate:nil];
- [textObject setSel:0 :0];
- font = [textObject font];
-
- return self;
- }
-
- /* Archiving methods */
-
- - awake
- {
- initClassVars();
- return [super awake];
- }
-
- - write:(NXTypedStream *)stream
- /*
- * Writes the TextGraphic out to the typed stream.
- */
- {
- [super write:stream];
- NXWriteTypes(stream, "i", &length);
- NXWriteArray(stream, "c", length, data);
- return self;
- }
-
- - read:(NXTypedStream *)stream
- /*
- * Reads the TextGraphic in from the typed stream.
- * This is versioned. The old way we used to implement
- * this class included using a Cell object. Now we
- * use the Text object directly.
- */
- {
- id cell;
- int maxlen;
- NXStream *s;
- char *buffer;
-
- [super read:stream];
- if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
- NXReadTypes(stream, "@", &cell);
- [drawText setText:[cell stringValue]];
- font = [cell font];
- [drawText setFont:[cell font]];
- [drawText setTextColor:[self lineColor]];
- s = NXOpenMemory(NULL, 0, NX_WRITEONLY);
- [drawText writeRichText:s];
- NXGetMemoryBuffer(s, &buffer, &length, &maxlen);
- NX_ZONEMALLOC([self zone], data, char, length);
- bcopy(buffer, data, length);
- NXCloseMemory(s, NX_FREEBUFFER);
- } else {
- NXReadTypes(stream, "i", &length);
- NX_ZONEMALLOC([self zone], data, char, length);
- NXReadArray(stream, "c", length, data);
- }
-
- return self;
- }
-
- @end
-